////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNNetworking/inc/WNLinuxListenConnection.h"
#include "WNNetworking/inc/WNLinuxINConnection.h"
#include "WNMemory/inc/WNManipulation.h"
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <fcntl.h>

using namespace WNNetworking;

WNLinuxListenConnection::WNLinuxListenConnection(WNNetworkManager& _manager, WNConnectionType _type, WN_UINT16 _port, WNConnectedCallback _connected) :
    WNLinuxConnection(_manager),
    mPort(_port),
    mType(_type),
    mConnectedCallback(_connected) {
}

WNLinuxListenConnection::~WNLinuxListenConnection() {
    
}

WNNetworkingError WNLinuxListenConnection::Initialize() {
    WN_RELEASE_ASSERT_DESC(mType == eWNReliable, "WNNetworking does not support unreliable connections .... YET");
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    WN_INT32 sock;

    WNMemory::WNMemSet(&hints, 0, sizeof(struct addrinfo));
    
    struct sockaddr_in serv_addr;
    WNMemory::WNMemSet(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port=htons(mPort);

    mSockFD = socket(AF_INET, SOCK_STREAM, 0);
    if(mSockFD == -1) {
        return(WNNetworking::eWNCannotAssociate);
    }
    struct linger flush;
    flush.l_onoff= 1;
    flush.l_linger=0;
    int err = setsockopt(mSockFD, SOL_SOCKET, SO_LINGER, &flush, sizeof(flush));
    if(err != 0) {
        return(WNNetworking::eWNCannotAssociate);
    }

    sock = ::bind(mSockFD, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(sock != 0) {
        return(WNNetworking::eWNCannotAssociate);
    }

    WN_INT32 flags;

    flags = fcntl(mSockFD, F_GETFL, 0);
    if(flags == -1) {
        return(WNNetworking::eWNCannotListen);
    }
   
    flags |= SOCK_NONBLOCK;

    if(fcntl (mSockFD, F_SETFL, flags) == -1) {
        return(WNNetworking::eWNCannotListen);
    }
   
    if(listen(mSockFD, SOMAXCONN) == -1) {
        return(WNNetworking::eWNCannotListen);
    }

    int nameLen = WN_SNPRINTF(NULL, 0, "Listen:%d", mPort);
    WN_CHAR* name = WNMemory::WNCallocT<WN_CHAR>(nameLen + 1);
    WN_SNPRINTF(name, nameLen+1, "Listen:%d", mPort);
    mConnectionName = name;

    return(WNNetworking::eWNOK);
}
WNLinuxINConnection* WNLinuxListenConnection::ReceiveConnection() {
     
    struct sockaddr addr;
    socklen_t len = sizeof(sockaddr);
    WN_INT32 sockFD;

    sockFD = accept(mSockFD, &addr, &len);
    if(sockFD == -1){
        if((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
            return(WN_NULL);
        } else {
            char* s = strerror(errno);
            perror(NULL);
            //REPORT THE ERROR OR SOMETHING.. COULD NOT ACCEPT CONNECTION
            return(WN_NULL);
        }
    }

    WNLinuxINConnection * conn = WN_NEW(WNLinuxINConnection(mManager));
    
    conn->Initialize(sockFD, addr, len);
    if(!mConnectedCallback.Execute(conn->GetName())) {
        WN_DELETE(conn);
    }
    
    return(conn);
}